原文地址 翻译:DeveloperLx
在现代的Android app开发中,fragment是构成UI布局非常的常用工具。在本教程中你会深入Android Fragment的基础概念,并创建一个app来展示暴走漫画。
孤立的或未完成的部分
fragment是一个Android的组件,用来管理activity中的一部分UI。正如它的名字所体现的,fragment并非是一个独立的实体,而是寄存在某个activity下。
在很多方面,fragment都有着类似于activity的特性。
想象一下,你是一个activity,你有很多事需要做,于是就雇佣了很多迷你的自己来运行,用洗衣和交税来换取住宿和食物。这就很像是activity和fragment之间的关系。
现在,可能就像实践上你并不需要几个听从命令的部下,你也并不一定需要使用fragment。然而,如果你可以很好地使用fragment,它就可以为你提供:
- 模块性:将复杂的activity代码拆分为一个个的fragment,以获得更好的组织性和可维护性。
- 可复用性:将行为或UI部分放置到多个fragment中,而fragment可以在多个activity之间进行共享。
- 可适应性:将UI的部分表示为不同的fragment,并根据屏幕的方向和尺寸使用不同的布局。
在本教程中,你将构建一个暴走漫画的迷你百科全书。app将展示一个由暴走漫画构成的格子视图。当选中一副暴走漫画的时候,app就会展示与它相关的信息。你会从中学到:
- 如何创建并添加fragment到activity上。
- 如何让fragment发送信息到activity上。
- 如何使用事务来添加或交换fragment。
注意 :本教程假定你已熟悉了Android编程的基础,并理解activity的生命周期的含义。还有几点值得去注意:
- 如果你是一个Android的纯小白,你应当首先参考 Android Tutorial for Beginners 和 Activity介绍 。
-
本教程还会用到Android的
RecyclerView
。如果你还从未用过RecyclerView
,你可以参考 Android RecyclerView Tutorial 来进行学习。
接下来就开始学习fragments!
下载 初始项目 ,解压并启动 Android Studio 3.0 Beta 2 或更高的版本。
在 Welcome to Android Studio 对话框中,选择 Import project (Eclipse ADT, Gradle, etc.) 。
选择初始项目的根目录,并点击 OK 。
如果看到了一个更新项目Gradle插件的信息,那是因为你使用了更高版本的Android Studio,选择“update”并继续。
查看项目,你会找到一些资源文件: strings.xml , activity_main.xml , drawable 和 layout 。还有一些供你fragment使用的模板布局文件,非fragment的代码,以及一个fragment类,你可以在之后进行加工。
MainActivity
会持有你所有小小的fragment,而
RageComicListFragment
则包含了用来展示暴走漫画内容列表的代码,这样你就可以将注意力集中到fragment本身了。
很快你就会修复...
和activity一样,fragment中也有着生命周期的概念,当fragment的状态发生变化的时候,就会触发相应的事件。例如,当fragment变为可见,活跃,无效,或被移除的状态时,一些事件就会发生。你可以在其中添加一些代码和行为来响应这些事件。
下面是 Android开发者文档 中,fragment生命周期的图表。
当你添加一个fragment时,就会触发下列的生命周期事件:
-
onAttach
:当fragment被依附到相应的activity上时被调用。 -
onCreate
:当一个新的fragment实例被初始化时调用,通常发生在它被附加到相应activity上后被调用 - fragment有一点像是病毒。 -
onCreateView
:当fragment创建了view层级中它自己的部分时,也就是被添加到了activity的view层级的时候被调用。 -
onActivityCreated
:当fragment的activity完成了它的onCreate
事件之后调用。 -
onStart
:当fragment变为可见状态后调用。fragment只会在它的activity启动之后才会启动,且通常都是activity一启动之后,它就启动。 -
onResume
:当fragment变为可见且可交互的状态后调用。fragment resume只会在它的activity resume之后,且通常都是activity一resume之后,它就resume。
但稍等,fragment还未完成。下列的生命周期的事件将在你移除一个fragment的时候发生:
-
onPause
:当fragment变为不可交互的状态时触发。它仅会在一个fragment将被移除或替代的时候,或其activity被pause的时候发生。 -
onStop
:当fragment变为不可见的状态时触发。它仅会在一个fragment将被移除或替代的时候,或其activity被停止的时候发生。 -
onDestroyView
:当fragment的view和创建在onCreateView
中的相关资源从activity的view层级中被移除并销毁的时候触发。 -
onDestroy
:当fragment执行最后的清理时调用 -
onDetach
:当fragment从所在的activity中移除的时候调用
正如你所看到的,fragment的生命周期始终伴随着activity的生命周期。但它还有一些相应于view层级,状态,附加/分离于activity的额外的事件。
在Android中,当使用fragment时,你可以使用两种fragment的实现。一种是由平台版本所提供的,也就是用户正在运行的Android的版本所提供。例如,一台运行Android 6.0(SDK版本23)的设备,就会运行库的平台版本23。
第二种是支持库的fragment。导入一个支持库到你的项目中和导入其它的第三方库没有什么差别。它在开发支持多版本Android的app时,有两个好处。
首先,它确保了你的代码在不同设备不同平台版本上的一致性。这就意味着bug的修复会在使用这些库的不同版本的Android上更加得一致。
其次,当新的特性被添加到最新版本的Android上时,Android的团队通常就会通过支持库来将其进行向后兼容,以便开发者使用较旧版本的Android。
如果你要编写支持多个版本Android和多个版本设备的app,你就应当为Fragment采取支持库的方案。你同样需要为其它的功能采用支持库的方案。这是大多数的高级Android开发者和Android核心团队所认为的最佳实践。唯有在你想为非常特定版本的Android做开发的时候,才会采用平台库的方案。
换句话就是:如果可以的话,就尽量使用支持库的方案。对于fragment,就是使用 the v4 support library 了。
最终,所有的暴走漫画会在启动的时候被展示位一个列表,点击其中的一项则会这幅漫画相应的详情。首先你将会创建详情页。
在Android Studio中打开初始项目,并在 app -> res -> layout 下找到 fragment_rage_comic_details.xml ,这个XML文件就指定了漫画详情展示的布局。它还展示了drawable资源和相关的string资源之一。
选择Android Studio的 Project tab 并找到 RageComicDetailsFragment 文件。这个类用来负责展示被选择漫画的展示详情。
在 RageComicDetailsFragment.kt 中,会看到类似如下的代码:
import android.os.Bundle
import android.support.v4.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
//1
class RageComicDetailsFragment : Fragment() {
//2
companion object {
fun newInstance(): RageComicDetailsFragment {
return RageComicDetailsFragment()
}
}
//3
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
return inflater?.inflate(R.layout.fragment_rage_comic_details, container, false)
}
}
上述代码:
-
声明
RageComicDetailsFragment
作为Fragment
的子类。 - 提供了一个方法用来创建这个fragment的实例,这是一个工厂方法。
- 创建由fragment所控制的view的层级。
Activity使用
setContentView()
来指定它相应的布局文件,而fragment则在
onCreateView()
中创建它们的view层级。这里你调用
LayoutInflater.inflate
来创建
RageComicDetailsFragment
的层级。
inflate
的第三个参数确定了是否要将其添加到
container
上。container就是将持有fragment的view层级的父view。你应当总是将其设置为
false
:
FragmentManager
将负责将fragment添加到container上。
这里有一个新的东东:
FragmentManager
。每个activity都会有一个
FragmentManager
来管理它的fragment。它还提供了一个供你访问,添加和移除的界面。
你会注意到尽管
RageComicDetailsFragment
有一个工厂的实例方法
newInstance()
,但却没有任何的构造器。
为何要有一个工厂方法而不是构造器?
首先,由于你并没有定义任何的构造器,编译器就会自动生成一个空的,无参的默认构造器。这就是你需要有的构造器了,无需其它。
第二,你可能知道,当app进入后台的时候,Android会销毁并重新创建Activity及其所属的fragment。当activity再生的时候,它的
FragmentManager
就会使用默认的空构造器来重建fragment。如果无法找到,就会抛出一个异常。
因此,最后的实践就是永远都不要指定非空的构造器,实际上,最简单的做法就是你刚刚做的这样,不要指定构造器。
稍等,如果你需要将信息或数据传递给Fragment,该怎么做?跟上,你马上就将得到答案。
添加你漂亮的fragment的最简单的办法,就是将它添加到XML的布局文件上。
打开 activity_main.xml ,选择Text tab,并添加下列的内容到根 FrameLayout 中:
<fragment
android:id="@+id/details_fragment"
class="com.raywenderlich.alltherages.RageComicDetailsFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
这里在activity布局的内部放置了一个
<fragment>
,并指定了fragment的类型。
class
这个属性需要完整。
<fragment>
的view ID是
FragmentManager
所需求的。
运行项目,你就可以看到fragment了:
首先,再次打开
activity_main.xml
并移除你刚放置的
<fragment>
。(是的,我知道你刚刚把它放到的这里 - 对不起。)你将使用暴走漫画的列表来替换它。
打开
RageComicListFragment.java
,它包含了所有可爱的列表代码。你可以看到
RageComicListFragment
没有显式的构造器,只有一个
newInstance()
。
RageComicListFragment
中的列表代码需要依赖于一些资源。你必须确保这个fragment对
Context
包含有效的引用。这就是
onAttach()
应该发挥作用的地方了。
打开 RageComicListFragment.kt ,并添加下列的import到已有import的下方:
import android.os.Bundle
import android.support.v7.widget.GridLayoutManager
GridLayoutManager
,用来在暴走漫画的列表中放置item。其它的import则是标准的fragment覆盖。
在
RageComicListFragment.kt
中,添加下列的两个方法,就在
RageComicAdapter
定义的上方:
override fun onAttach(context: Context?) {
super.onAttach(context)
// Get rage face names and descriptions.
val resources = context!!.resources
names = resources.getStringArray(R.array.names)
descriptions = resources.getStringArray(R.array.descriptions)
urls = resources.getStringArray(R.array.urls)
// Get rage face images.
val typedArray = resources.obtainTypedArray(R.array.images)
val imageCount = names.size
imageResIds = IntArray(imageCount)
for (i in 0..imageCount - 1) {
imageResIds[i] = typedArray.getResourceId(i, 0)
}
typedArray.recycle()
}
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
val view: View = inflater!!.inflate(R.layout.fragment_rage_comic_list, container,
false)
val activity = activity
val recyclerView = view.findViewById<RecyclerView>(R.id.recycler_view) as RecyclerView
recyclerView.layoutManager = GridLayoutManager(activity, 2)
recyclerView.adapter = RageComicAdapter(activity)
return view
}
onAttach()
中,访问了你需要通过
Context
来访问的资源。由于代码位于
onAttach()
中,你无需判断fragment是否包含一个有效的
Context
。
在
onCreateView()
中,你填充了
RageComicListFragment
的view的层级,它包含了一个
RecyclerView
,并执行了一些设置。
通常,如果你需要在fragment的view上进行一些处理,
onCreateView()
就是一个很好的地方,因为这里已经能确定你的view准备好了。
接下来,打开
MainActivity.kt
并使用下列的代码替换
onCreate()
方法:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (savedInstanceState == null) {
supportFragmentManager
.beginTransaction()
.add(R.id.root_layout, RageComicListFragment.newInstance(), "rageComicList")
.commit()
}
}
这里你将
RageComicListFragment
放置到
MainActivity
中。你会请求你的新朋友
FragmentManager
来添加它。
首先,你通过
supportFragmentManager
而非
fragmentManager
得到了
FragmentManager
,因为你使用的是支持框架。
然后通过调用
beginTransaction()
来请求
FragmentManager
开启一个新的事务 - 你应该可以单靠自己搞懂了。然后调用
add
来指定你想要的添加操作,并传递参数:
-
用来在activity的布局中,容纳fragment的view层级的container的view ID。如果你偷偷看一眼
activity_main.xml
,你就会发现@+id/root_layout
。 - 待添加的fragment的实例。
-
一个字符串,用来充当fragment实例的tag/identifier。这样便于
FragmentManager
在稍后检索fragment。
最后,你通过调用
commit()
来请求
FragmentManager
执行事务。
这样,fragment就被添加上了!
运行项目,你就会看到充满暴走漫画的列表:
FragmentManager
通过
FragmentTransactions
实现功能,它们是fragment基本的操作,如添加,删除等等。
在上述的代码中,
if
语句包含了展示fragment的代码,并判断activity尚无保存的状态。当activity被保存的时候,相应它所有fragment也会被保存。如果你不执行这这检查,就会发生这样的情况:
你就会:
课程:牢记保存的状态会如何影响你的fragment。
环视项目,你会注意到一些事情:
-
一个叫做
DataBindingAdapters
的文件。 -
在app的模块
build.gradle
中,有一个dataBinding
的引用:dataBinding { enabled = true }
-
在
recycler_item_rage_comic.xml
这个布局文件中的一个data的部分。<layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="comic" type="com.raywenderlich.alltherages.Comic" /> </data> ... </layout>
-
A
Comic
的数据类。
如果你没有使用 数据绑定 ,可能就会像...
让我们来快速地看一遍。
通常,如果你想要设置在布局文件中的值,你就会在fragment和activity中使用类似如下的代码:
programmer.name = "a purr programmer"
view.findViewById<TextView>(R.id.name).setText(programmer.name)
问题是,如果你改变了
programmer
中
name
的值,你也需要对
setText
做一个相应的
programmer
来更新此对象。想象一下,如果有一个工具,可以将你一个在fragment和activity中的变量和相应的view绑定起来,只要这个变量一改变,相应的view也就会发生变化。这就是
数据绑定
会为你做的事。
而在我们的app的
build.gradle
中,设置的
enabled=true
就打开了app的
数据绑定
。而数据类就包含了我们想在fragment中使用,并展示到view上的数据。
data
域中包含了
name
和
type
选项,指定了被绑定变量的类型和名称。这个数据在view中使用了
{@}
符号。例如,下列的代码就将text域的值绑定到了
comic
变量的
name
字段上:
tools:text="@{comic.name}"
设置好view之后,你就需要访问你的view,并将变量
绑定到
它上面。这就是
数据绑定
的魔法将要出现的地方了!只要view有一个
data
字段,framework就会自动生成绑定的对象。通过将view的下划线方式的名称转换为驼峰式的名称,来推断对象的名称,并添加
绑定
到这个名称上。例如,一个被称作
recycler_item_rage_comic.xml
的view就会拥有一个被称作
RecyclerItemRageComicBinding
的绑定。
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder {
//1
val recyclerItemRageComicBinding = RecyclerItemRageComicBinding.inflate(layoutInflater,
viewGroup, false)
//2
val comic = Comic(imageRmesIds[position], names[position], descriptions[position],
urls[position])
recyclerItemRageComicBinding.comic = comic
您可以通过在 绑定 对象上inflater的方法来填充view,并通过标准property访问机制来设置property。
数据绑定遵循了Model-View-ViewModel(MVVM)模式。MVVM包含三个组件:
- A View :布局文件。
- A Model :数据类
- A View Model/Binder :自动生成的绑定文件。
关于MVVM及其它的设计模式,请访问教程: Common Design Patterns for Android 。你会看到更多关于数据绑定的内容。
即使fragment被附加到了一个activity上,如果没有进一步的“鼓励”,它们就不一定要彼此交流。
对于所有的暴走漫画,你需要
RageComicListFragment
可以让
MainActivity
知道什么时候用户已作出了选择,这样
RageComicDetailsFragment
才可以将选择展示出来。
开始,打开 RageComicListFragment.kt 并添加下列的Java interface到文件的底部:
interface OnRageComicSelected {
fun onRageComicSelected(comic: Comic)
}
这就为activity定义了一个监听者的interface来监听fragment。activity将实现这个interface,而fragment就会在一项被选中时,调用
onRageComicSelected()
,将选择传递给activity。
在
RageComicListFragment
中,现有的字段下添加一个新的字段:
private lateinit var listener: OnRageComicSelected
这个字段会引用fragment的监听者,也就是activity。
在
onAttach()
方法中,
super.onAttach(context);
之下,添加下列代码:
if (context is OnRageComicSelected) {
listener = context
} else {
throw ClassCastException(context.toString() + " must implement OnRageComicSelected.")
}
这里初始化了监听者的引用。在
onAttach()
中执行,可以确保fragment确实被附加到了activity上。然后你通过
instanceof
来验证相应的activity实现了
OnRageComicSelected
interface。
如果判断失败,就抛出一个异常,因为你已无法继续下去了。反之,则将activity设置为
RageComicListFragment
的
listener
。
在
onBindViewHolder()
方法中,添加下列的代码到它的底部 -- ok,我撒了一点小谎:
RageComicAdapter
并没有包含你所需的
所有内容
!)
viewHolder.itemView.setOnClickListener { listener.onRageComicSelected(comic) }
这就添加了一个
View.OnClickListener
到每个暴走漫画上,以便它在listener(activity)上调用回调方法,来传递选择。
打开 MainActivity.java 并将类定义更新为如下的代码:
class MainActivity : AppCompatActivity(), RageComicListFragment.OnRageComicSelected {
这里会报出一个错误,让你将
MainActivity
设为abstract的,或实现abstract的方法
OnRageComicSelected(int, String, String, String)
。不要纠结这里,很快你就会解决它。
此代码指定了
MainActivity
会作为
OnRageComicSelected
interface的一个实现。
现在,你将展示一个toast来验证代码是否可以正常地工作。添加下列的import到已有的import下面:
import android.widget.Toast
然后在
onCreate()
方法之后,添加下列的方法:
override fun onRageComicSelected(comic: Comic) {
Toast.makeText(this, "Hey, you selected " + comic.name + "!",
Toast.LENGTH_SHORT).show()
}
现在错误就消失了!运行项目,然后点击任一个暴走漫画,你就会看到一个被点击项目的toast消息:
你已经让activity和它的fragment进行沟通了。你就像是一个数字外交官!
当前,
RageComicDetailsFragment
展示了一个静态的
Drawable
和
Strings
的集合,但你还想展示用户的选择。
首先,将在
fragment_rage_comic_details.xml
中全部的view替换为:
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="comic"
type="com.raywenderlich.alltherages.Comic" />
</data>
<ScrollView xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
tools:ignore="RtlHardcoded">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical">
<TextView
android:id="@+id/name"
style="@style/TextAppearance.AppCompat.Title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="0dp"
android:layout_marginTop="@dimen/rage_comic_name_margin_top"
android:text="@{comic.name}" />
<ImageView
android:id="@+id/comic_image"
android:layout_width="wrap_content"
android:layout_height="@dimen/rage_comic_image_size"
android:layout_marginBottom="@dimen/rage_comic_image_margin_vertical"
android:layout_marginTop="@dimen/rage_comic_image_margin_vertical"
android:adjustViewBounds="true"
android:contentDescription="@null"
android:scaleType="centerCrop"
imageResource="@{comic.imageResId}" />
<TextView
android:id="@+id/description"
style="@style/TextAppearance.AppCompat.Body1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="@dimen/rage_comic_description_margin_bottom"
android:layout_marginLeft="@dimen/rage_comic_description_margin_left"
android:layout_marginRight="@dimen/rage_comic_description_margin_right"
android:layout_marginTop="0dp"
android:autoLink="web"
android:text="@{comic.text}" />
</LinearLayout>
</ScrollView>
</layout>
我们在顶端为 Comic 添加了一个variable,将 name 和 description 绑定到了 Comic 对象中同名的变量。
在暴走漫画的ImageView中,你会注意到如下的标签:
imageResource="@{comic.imageResId}"
这就对应于我们在
DataBindingAdapters.kt
文件中创建的绑定Adapter。
@BindingAdapter("android:src")
fun setImageResoruce(imageView: ImageView, resource: Int) {
imageView.setImageResource(resource)
}
我们可以通过
binding adapter
在不被默认的
数据绑定
支持的元素上执行动作。这里为展示图片储存了一个resource的整型值,但数据绑定无法提供一个默认的方式来根据一个ID展示图片。要修复这个问题,你需要一个
BindingAdapter
,它引用了一个被调用的对象,及一个参数。用它来调用
imageView
上的
setImageResource
来展示暴走漫画。
现在view已经被设置好了,添加下列的import到 RageComicDetailsFragment.kt 文件的顶部:
import java.io.Serializable
将
newInstance()
替换为如下的代码:
private const val COMIC = "comic"
fun newInstance(comic: Comic): RageComicDetailsFragment {
val args = Bundle()
args.putSerializable(COMIC, comic as Serializable)
val fragment = RageComicDetailsFragment()
fragment.arguments = args
return fragment
}
fragment可以通过
arguments
这个fragment来获取初始化的参数。arguments实际上是一个用来储存键值对的
Bundle
,就像是在
Activity.onSaveInstanceState
中的
Bundle
。
你创建并填充了arguments的
Bundle
,并设置给
arguments
,当你在后面需要value的时候,你就引用
arguments
property来获取它。
就像你在前面学到的,当fragment被重新创建的时候,会使用默认的空构造器 - 无需参数。
由于fragment可以从它的持久化参数中重新调用初始化参数,你就可以在重新创建的过程中使用它们。上述的代码还储存了在
RageComicDetailsFragment
参数中保存的被选中的暴走漫画的信息。
添加下列的import到 RageComicDetailsFragment.kt 文件顶部:
import com.raywenderlich.alltherages.databinding.FragmentRageComicDetailsBinding
将 onCreateView() 的内容替换为如下代码:
val fragmentRageComicDetailsBinding = FragmentRageComicDetailsBinding.inflate(inflater!!,
container, false)
val comic = arguments.getSerializable(COMIC) as Comic
fragmentRageComicDetailsBinding.comic = comic
comic.text = String.format(getString(R.string.description_format), comic.description, comic.url)
return fragmentRageComicDetailsBinding.root
由于你希望根据选择动态性地填充
Since you want to dynamically populate the UI of the
RageComicDetailsFragment
的UI,因此首先在fragment的
onCreateView
中获取
FragmentRageComicDetailsBinding
的引用。然后,将你传给
RageComicDetailsFragment
的暴走漫画和相应的view进行绑定。
最后,在用户点击一项的时候,创建并展示一个
RageComicDetailsFragment
,替换仅仅展示toast。打开
MainActivity
并将
onRageComicSelected
中的逻辑替换为:
val detailsFragment =
RageComicDetailsFragment.newInstance(comic)
supportFragmentManager.beginTransaction()
.replace(R.id.root_layout, detailsFragment, "rageComicDetails")
.addToBackStack(null)
.commit()
你会发现,这里非常类似于之间将列表添加到
MainActivity
的代码,但也有一些值得注意的区别。
- 创建一个包含一些漂亮参数的fragment的实例。
-
调用
replace()
而不是add
,来移除当前容器中的fragment,并添加新的Fragment。 -
调用了另一个新朋友:
FragmentTransaction
中的addToBackStack()
。Fragment拥有 back栈 ,或是历史记录,就像Activity一样。
fragment的back栈并不独立于activity的back栈。它是宿主activity历史记录的一部分。
当你在activity之间进行切换的时候,每个activity都会被放置在back栈上。每当你commit一个
FragmentTransaction
时,你就可以选择将它添加到back栈上。
那么,
addToBackStack()
做了什么呢?它添加了
replace()
到back栈上,这样当用户点击返回按钮的时候,就可以进行撤销事务了。在本例中,点击返回按钮,用户就可以回到列表场景中。
列表的
add()
事务则会忽略调用
addToBackStack()
。这意味着事务是相同历史记录(整个activity)的一部分。如果用户在列表场景中点击返回按钮,就出跳出app。
现在,运行项目,当你点击其中一项的时候,你就可以看到该暴走漫画的详情了:
完工了!现在你就有了一个可以展示暴走漫画详情的 All The Rages app了。
你可以从 这里 下载最终完成的项目。
关于fragment还有 很多 值得学习的地方。和任何一种工具或特性一样,考虑fragment是否适合你app的需求,如果是的话,尝试遵循最佳的实践和惯例。
为了让你的技能提升到一个新的台阶,以下有一些额外的事可以进行探索:
-
在
ViewPager
中使用fragment。很多app,包括Play Store,会通过ViewPager
来使用一个可滑动,分页式的内容结构。 -
使用更强大的
DialogFragment
来替换普通的“香草”对话框或AlertDialog
。 - 演练fragment如何与Activity的其它部分进行交互,例如app的工具栏。
- 用fragment创建适应性的UI。实际上,你应当去运行 Adaptive UI in Android Tutorial 。
- 使用fragment作为实现高级行为架构的一部分。你可以参考一下 Common Design Patterns for Android 来作为一个很好的起点,让架构搭建起来。